/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/listen.c,v 1.23 2003/07/28 11:03:02 bboy Exp $

	Copyright (C) 2002, 2003  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"
#if HAVE_NET_IF_H
#  include <net/if.h>
#endif
#  include <sys/ioctl.h>

/* Listening sockets (TCP/UDP) */
int *udp_fd = (int *)NULL;
int *tcp_fd = (int *)NULL;

/* Number of listening sockets of each type */
int num_udp_fd = 0;
int num_tcp_fd = 0;

extern char	*opt_conf;
extern uint32_t answer_then_quit;

static void server_greeting(void);


/**************************************************************************************************
	LISTENER
	Create server socket, return fd.
**************************************************************************************************/
static int
listener(struct sockaddr_in *sa, int protocol)
{
	int fd, opt = 1;

	if ((fd = socket(AF_INET, protocol, (protocol == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP)) < 0)
		Err("socket");
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
		Warn("setsockopt");
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);

	if (bind(fd, (struct sockaddr *)sa, sizeof(struct sockaddr)) < 0)
		Err("bind (%s): %s:%d",
			 (protocol == SOCK_STREAM) ? "TCP" : "UDP", inet_ntoa(sa->sin_addr), ntohs(sa->sin_port));
	if (protocol == SOCK_STREAM)
	{
		if (listen(fd, 5) < 0)
			Err("listen");
	}
	else
	{
		int n, size;
		for (n = 1; n < 1024; n++)
		{
			size = n * 1024;
			if ((setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))) < 0)
				break;
		}
#if DEBUG_ENABLED
		Debug("listening on %s:%d", inet_ntoa(sa->sin_addr), ntohs(sa->sin_port));
#endif
	}
	return (fd);
}
/*--- listener() --------------------------------------------------------------------------------*/


/**************************************************************************************************
	GET_ALL_INTERFACES
	Get all interfaces, adding them to the provided list.  A new pointer to the provided list
	is returned, and "numlist" is updated.
**************************************************************************************************/
static struct sockaddr_in **
get_all_interfaces(struct sockaddr_in **list, int port, int *numlist)
{
	struct ifconf ifc;
	int sockfd, numifaces = 32, n;
	unsigned char *buf;

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		Err("socket");
	if (!(buf = malloc(sizeof(struct ifreq) * numifaces)))
		Err("malloc");

	for (;;)															/* Allocate buffer space */
	{
		ifc.ifc_len = sizeof(struct ifreq) * numifaces;
		ifc.ifc_buf = buf;
		if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
			Err("ioctl(SIOCGIFCONF)");
		if ((ifc.ifc_len / sizeof(struct ifreq)) == numifaces)
		{
			numifaces *= 2;
			if (!buf)
			{
				if (!(buf = malloc(sizeof(struct ifreq) * numifaces)))
					Err("malloc");
			}
			else
			{
				if (!(buf = realloc(buf, sizeof(struct ifreq) * numifaces)))
					Err("realloc");
			}
		}
		else break;
	}

	for (n = 0; n < ifc.ifc_len;)
	{
		struct sockaddr_in *sa;
		struct ifreq *ifr = (struct ifreq *)((char *)ifc.ifc_req + n);

#ifdef HAVE_SOCKADDR_SA_LEN
		n += sizeof(ifr->ifr_name) + (ifr->ifr_addr.sa_len > sizeof(struct sockaddr)
												? ifr->ifr_addr.sa_len : sizeof(struct sockaddr));
#else
		n += sizeof(struct ifreq);
#endif /* HAVE_SOCKADDR_SA_LEN */

		if (ifr->ifr_addr.sa_family != AF_INET)			/* Must be AF_INET */
			continue;
		if (ifr->ifr_flags & IFF_UP)							/* Must be up */
			continue;
		if (ioctl(sockfd, SIOCGIFADDR, ifr) < 0)			/* Get address */
			Err("ioctl(SIOCGIFADDR)");

		/* Expand list */
		if (!list)
		{
			if (!(list = malloc(1 * sizeof(struct sockaddr_in *))))
				Err("malloc");
		}
		else
		{
			if (!(list = realloc(list, ((*numlist) + 1) * sizeof(struct sockaddr_in *))))
				Err("realloc");
		}
		if (!(list[(*numlist)] = malloc(sizeof(struct sockaddr_in))))
			Err("malloc");
		sa = list[(*numlist)];
		*numlist = (*numlist) + 1;

		/* Load struct sockaddr_in */
		memset(sa, 0, sizeof(struct sockaddr_in));
		sa->sin_family = AF_INET;
		sa->sin_port = htons(port);
		memcpy(&sa->sin_addr, &((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr, sizeof(struct in_addr));
	}

	close(sockfd);
	free(buf);
	return (list);
}
/*--- get_all_interfaces() ----------------------------------------------------------------------*/


/**************************************************************************************************
	SET_ADDRESSES
	Examines the provided configuration option and returns an array of sockaddr_in structures.
**************************************************************************************************/
static struct sockaddr_in **
set_addresses(const char *opt, int default_port, int *found)
{
	struct sockaddr_in **list = NULL;						/* List to return */
	char *optcopy, *c, *port, *addr;

	*found = 0;
	if (!opt)
		return (NULL);

	if (!(optcopy = strdup((char *)opt)))
		Err("strdup");

	/* Replace commas with CONF_FS */
	for (c = optcopy; *c; c++)
		if (*c == ',')
			*c = CONF_FS_CHAR;

	/* Add each (comma-separated) address[:port] specification */
	for (c = optcopy; (addr = strsep(&c, CONF_FS_STR)); )
	{
		struct sockaddr_in *sa;
		int portnum = 53;

		if ((port = strchr(addr, ':')))
		{
			*port++ = '\0';
			if (*port)
				portnum = atoi(port);
		}

		/* If the address specifies the wildcard '*', add a record for each interface */
		if (addr[0] == '*')
		{
			list = get_all_interfaces(list, portnum, found);
			continue;
		}

		/* Handle "localhost" */
		if (!strcasecmp(addr, "localhost"))
			addr = "127.0.0.1";

		/* Expand the list */
		if (!list)
		{
			if (!(list = malloc(1 * sizeof(struct sockaddr_in *))))
				Err("malloc");
		}
		else
		{
			if (!(list = realloc(list, ((*found) + 1) * sizeof(struct sockaddr_in *))))
				Err("realloc");
		}
		if (!(list[(*found)] = malloc(sizeof(struct sockaddr_in))))
			Err("malloc");
		sa = list[(*found)];
		*found = (*found) + 1;

		/* Convert address[:port] information to struct sockaddr_in */
		memset(sa, 0, sizeof(struct sockaddr_in));
		sa->sin_family = AF_INET;
		sa->sin_port = htons(portnum);
		if (inet_pton(AF_INET, addr, (void *)&sa->sin_addr) <= 0)
			Errx("%s: \"%s\": %s", opt_conf, addr, _("error interpreting IP address"));
	}
	free(optcopy);
	return (list);
}
/*--- set_addresses() ---------------------------------------------------------------------------*/


/**************************************************************************************************
	CREATE_LISTENERS
**************************************************************************************************/
void
create_listeners(void)
{
	char	*port_opt = conf_get(&Conf, "port", NULL);
	int	port = 53;
	struct sockaddr_in **listen_addr = NULL;				/* Listen on these addresses */
	struct sockaddr_in **no_listen_addr = NULL;			/* Don't listen on these addresses */
	int	num_listen_addr = 0;									/* Number of addresses in 'listen_addr' */
	int	num_no_listen_addr = 0;								/* Number of addresses in 'no_listen_addr' */
	int	n, y;

	/* Set default port number */
	if (port_opt && atoi(port_opt))
		port = atoi(port_opt);

	/* Set 'listen_addr' and 'no_listen_addr' from 'listen' and 'no-listen' config options */
	listen_addr = set_addresses(conf_get(&Conf, "listen", NULL), port, &num_listen_addr);
	no_listen_addr = set_addresses(conf_get(&Conf, "no-listen", NULL), port, &num_no_listen_addr);

	/* Remove any addresses in 'no_listen_addr' from 'listen_addr' */
	for (n = 0; n < num_no_listen_addr; n++)
		for (y = 0; y < num_listen_addr; y++)
			if (listen_addr[y] && no_listen_addr[n]
				 && (listen_addr[y]->sin_addr.s_addr == no_listen_addr[n]->sin_addr.s_addr)
				 && (listen_addr[y]->sin_port == no_listen_addr[n]->sin_port))
			{
#if DEBUG_ENABLED
				Debug("Removing %s:%d from listen_addr", inet_ntoa(listen_addr[y]->sin_addr),
						ntohs(listen_addr[y]->sin_port));
#endif
				free(listen_addr[y]);
				listen_addr[y] = NULL;
			}

	/* Create listener for each address in listen_addr */
	for (n = 0; n < num_listen_addr; n++)
	{
		struct sockaddr_in *sa = listen_addr[n];

		if (!sa)
			continue;

		/* Create UDP listener */
		if (!num_udp_fd)
		{
			if (!(udp_fd = malloc(sizeof(int))))
				Err("malloc");
		}
		else
		{
			if (!(udp_fd = realloc(udp_fd, (1 + num_udp_fd) * sizeof(int))))
				Err("realloc");
		}
		udp_fd[num_udp_fd++] = listener(sa, SOCK_DGRAM);

		/* Create TCP listener if we need TCP */ 
		if (axfr_enabled || tcp_enabled)
		{
			if (!num_tcp_fd)
			{
				if (!(tcp_fd = malloc(sizeof(int))))
					Err("malloc");
			}
			else
			{
				if (!(tcp_fd = realloc(tcp_fd, (1 + num_tcp_fd) * sizeof(int))))
					Err("realloc");
			}
			tcp_fd[num_tcp_fd++] = listener(sa, SOCK_STREAM);
		}
	}

	server_greeting();

	/* Free the lists */
	for (n = 0; n < num_no_listen_addr; n++)
		free(no_listen_addr[n]);
	free(no_listen_addr);
	for (n = 0; n < num_listen_addr; n++)
		free(listen_addr[n]);
	free(listen_addr);
}
/*--- create_listeners() ------------------------------------------------------------------------*/


/**************************************************************************************************
	SERVER_GREETING
**************************************************************************************************/
static void
server_greeting(void)
{
	char greeting[512], *g = greeting;

	g += sprintf(g, "%s %s %s", progname, PACKAGE_VERSION, _("ready"));

	g += sprintf(g, " (%s %d %s)", _("listening on"),
					 num_udp_fd, num_udp_fd == 1 ? _("address") : _("addresses"));

#if DEBUG_ENABLED
	g += sprintf(g, "%s", _(" (compiled with debug)"));
#endif

	if (answer_then_quit)
		g += sprintf(g, _(" (quit after %u requests)"), answer_then_quit);

	Notice("%s", greeting);
}
/*--- server_greeting() -------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
